/**
 * Copyright (C) 2011  Robin Dietrich, Nils Kenneweg, Leonard Stutz
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/

#include <iostream>
#include <cmath> //for cos
#include <stdarg.h>


#include "mapGenerator.hpp"

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

/*
 TODO:
 - noise3D
 - mapGenerator Singleton
 - someHow Manage to write to surrounding chunks
	- mayBe have a Basic MapGenerator, which generates the Basic Terrain
	- and a Structure Generator
 - or write basic data to the file prior to really "generating" it.
*/

namespace minecraft {
    mapGenerator::mapGenerator() : gener(1) , distribution(-1,1), randomr(gener, distribution) {
		this->Seed = "bla";
    }
	
	mapGenerator::~mapGenerator() {
    }
	
	void mapGenerator::setSeed(std::string theSeed) {
		this->Seed = theSeed;
	}
	
	/**
	* Generates a random number between -1 and 1 for the given seed and ints put after the seed.
	*/
	double mapGenerator::noiseRand(int num, std::string addseed, ...) {
		va_list arguments; // A place to store the list of arguments
	
		va_start ( arguments , addseed ); // Initializing arguments to store all values after num
		
		std::size_t seed = 0;	
		
		//add all integers given to the function
		for (int i=1;i<=num;i++)
		{
				boost::hash_combine(seed, va_arg ( arguments, int )); // Adds the next value in argument list to
		}

		va_end ( arguments ); // Cleans up the list
		
		//add the seed given to the function
		boost::hash_combine(seed, addseed);
		
		//we need to lock the randomization functions.
		boost::lock_guard<boost::mutex> lock(this->lock);
		randomr.distribution().reset();
		
		randomr.engine().seed(abs((int)seed));
		double rand = randomr();
		
		return rand;
	}
	
    double mapGenerator::interpolate(double random1, double random2, double diff) {
        double ft = diff * 3.1415927;
        double f = (1.0 - cos((sin(ft)+ft/2)*2)) * 0.5;
        return random1 * (1.0 - f) + random2 * f;
    }
    
	/**
	* Three dimensional perlin noise.
	*/
	double mapGenerator::InterpolatedNoise(double d1, double d2, double d3) {
		int int_d1 = (int) floor(d1);
		int int_d2 = (int) floor(d2);
		int int_d3 = (int) floor(d3);
	
		double fractional_d1 = d1 - int_d1;
		double fractional_d2 = d2 - int_d2;
		double fractional_d3 = d3 - int_d3;
	
		//Group those which change in coordinate 3 but not another.
		double v1_d3_1 = this->noiseRand(3, this->getWorldSeed(), int_d1+0, int_d2+0, int_d3+0);
		double v2_d3_1 = this->noiseRand(3, this->getWorldSeed(), int_d1+0, int_d2+0, int_d3+1);
		
		double v1_d3_2 = this->noiseRand(3, this->getWorldSeed(), int_d1+0, int_d2+1, int_d3+0);
		double v2_d3_2 = this->noiseRand(3, this->getWorldSeed(), int_d1+0, int_d2+1, int_d3+1);
		
		double v1_d3_3 = this->noiseRand(3, this->getWorldSeed(), int_d1+1, int_d2+0, int_d3+0);
		double v2_d3_3 = this->noiseRand(3, this->getWorldSeed(), int_d1+1, int_d2+0, int_d3+1);
		
		double v1_d3_4 = this->noiseRand(3, this->getWorldSeed(), int_d1+1, int_d2+1, int_d3+0);
		double v2_d3_4 = this->noiseRand(3, this->getWorldSeed(), int_d1+1, int_d2+1, int_d3+1);
		
		//std::String theSeed
		
		//Interpolate those which change in coordinate 2, but not in coordinate 1
		double v1_d2_1 = this->interpolate(v1_d3_1, v2_d3_1, fractional_d3);
		double v2_d2_1 = this->interpolate(v1_d3_2, v2_d3_2, fractional_d3);
		
		double v1_d2_2 = this->interpolate(v1_d3_3, v2_d3_3, fractional_d3);
		double v2_d2_2 = this->interpolate(v1_d3_4, v2_d3_4, fractional_d3);
		
		//Interpolate those which change the 1st coordinate.
		double v1_d1_1 = this->interpolate(v1_d2_1, v2_d2_1, fractional_d2);
		double v2_d1_1 = this->interpolate(v1_d2_2, v2_d2_2, fractional_d2);
		
		double result = this->interpolate(v1_d1_1, v2_d1_1, fractional_d1);
		
		return result;
	}
	
	/**
	* get the weight at a certain position.
	*/
	double mapGenerator::weight(int height) {
		int MAXIMUM_HEIGHT = 130;
		int MINIMUM_HEIGHT = 60;
		
		if (height < MINIMUM_HEIGHT) {
			return 1;
		}
		
		if (height > MAXIMUM_HEIGHT) {
			return -1;
		}
		
		int diff = (height - MINIMUM_HEIGHT) ;
		int points = MAXIMUM_HEIGHT - MINIMUM_HEIGHT;
		
		return - (double) diff / (double) points * 2.0 + 1.0;
	}
	
	
	/** Noise for a certain coordinate.
	*/
    double mapGenerator::noise(double d1, double d2) {
        //floor both integers
        double fd1 = floor(d1);
        double fd2 = floor(d2);

        //for 1st and 2nd dimension, values are required for interpolation as we
        //need to get the surrounding pixels
        double a,b,c,d;
        a = this->noiseRand(2, this->getWorldSeed(), (int) fd1, (int) fd2);
        b = this->noiseRand(2, this->getWorldSeed(), (int) fd1+1, (int) fd2);
        c = this->noiseRand(2, this->getWorldSeed(), (int) fd1, (int) fd2+1);
        d = this->noiseRand(2, this->getWorldSeed(), (int) fd1+1, (int) fd2+1);
		
        //interpolate between values

		double i1 = this->interpolate(a, b, d1-fd1);
		double i2 = this->interpolate(c, d, d1-fd1);		
			
		return (this->interpolate(i1, i2, d2-fd2));
    }

	
	/**
		Returns perlin noise for a certain coordinate with given parameters
		@param x: x-coordinate
		@param y: y-coordinate
		@param top: a value for how high the noise should get
		@param iterations: how often to repeat noising
		@param zoom: zoom's the generated perlin noise.
		@param persistence: only applicable if iterations > 1.
	*/
	double mapGenerator::getPerlinNoise(int x, int y, unsigned int top, unsigned int iterations, double zoom, double persistence) {
        double noise = 0; 
		
		//std::cout << (double)x << "\n";
		
        for (unsigned int k = 0; k < iterations; ++k) {
			//increase frequency and persistence each iteration
			double fplus = pow(2, k);
			double pplus = pow(persistence, k);
			
			//add to noise
			noise += this->noise(((double)x)*fplus/zoom, ((double)y)*fplus/zoom)*pplus;
        }
		
		//std::cout << " X:" << x << " Y:" << y << " Noise:" << noise << "\n";

		noise = (noise * top + top) / 2;
		
        return noise;
    }
	
	double mapGenerator::get3DPerlinNoise(int x, int y, int z, unsigned int iterations, double zoom, double persistence) {
		double noise = 0; 
		
		//std::cout << (double)x << "\n";
		
        for (unsigned int k = 0; k < iterations; ++k) {
			//increase frequency and persistence each iteration
			double frequency = pow(2, k) / zoom;
			double amplitude = pow(persistence, k);
			
			//add to noise
			noise += this->InterpolatedNoise(((double)x) * frequency, ((double)y) * frequency, ((double)z) * frequency) * amplitude;
        }
		
		//std::cout << " Noise:" << noise << "\n";
		
        return noise;
	}
	
	int mapGenerator::heightAt(int x, int cx, int z, int cz, h_array &hmap) {
		//return this->heightAt(x, cx, z, cz);
		if (0 <= x && x < P_CHUNKSIZE && 0 <= z && z < P_CHUNKSIZE) {
			return hmap[x][z];
		} else {
			return this->heightAt(x, cx, z, cz);
		}
	}
	
	int mapGenerator::heightAt(int x, int cx, int z, int cz) {
		unsigned int iterations = 3;
		double zoom = 30.0;
		double persistence = 0.5;
		
		return 	this->getPerlinNoise(cx+x,cz+z, 16, iterations, zoom, persistence) + 30.0;
	}
	
	void mapGenerator::generate(b_array &toGenerate, int cx, int cy, int cz) {
		h_array hmap (boost::extents[P_CHUNKSIZE][P_CHUNKSIZE]);
		
	    unsigned long long t1;
		unsigned long long t2;
		
		cx = cx * P_CHUNKSIZE;
		cy = cy * P_CHUNKSIZE;
		cz = cz * P_CHUNKSIZE;
		
		t1 = rdtsc();
		
		unsigned int iterations = 3;
		double zoom = 30.0;
		double persistence = 0.5;
		
		double height = 0;
		
		for (int x = 0; x < P_CHUNKSIZE; x++) {
			for (int z = 0; z < P_CHUNKSIZE; z++) {
				for (int y = 0; y < P_CHUNKSIZE; y++) {
					//std::cout << " X:" << x << " Y:" << y << " Z:" << z;
					
					//std::cout << "Weight at " << cy+y << ":" << weight(cy+y) << "\n";
					
					double value = 0;
					
					if (weight(cy+y) == 1 || weight(cy+y) == -1) {
						//std::cout << "IF" << "\n";
						value = weight(cy+y);
					} else {
						value = get3DPerlinNoise(cx+x, cy+y, cz+z, 3, 100, 0.5);
						value = weight(cy+y) + value;
						
						//std::cout << cx+x << ":" << cy+y << ":" << cz+z << " Weight:" << weight(cy+y) << " Perlin:" << value;
						//std::cout << " Real Value:" << value  << "\n";
					}
					
					if (value < 0) {
						toGenerate[x][y][z] = 0;
					} else {
						if (value > 0.2) {
							toGenerate[x][y][z] = 1;
						} else {
							toGenerate[x][y][z] = 3;
						}
					}
				}
			}
		}
		
		t2 = rdtsc();
		
		//std::cout << "Generating chunk done! [" << (P_CHUNKSIZE * P_CHUNKSIZE) <<"] (" << (t2-t1) / 1000000 << ")\n";
	}
	
	bool mapGenerator::overhang(int x, int cx, int z, int cz, h_array &hmap) {
			if (heightAt(x+1, cx, z, cz, hmap) < heightAt(x, cx, z, cz, hmap) && heightAt(x-1, cx, z, cz, hmap) < heightAt(x, cx, z, cz, hmap))
				return false;
				
			if (heightAt(x, cx, z+1, cz, hmap) < heightAt(x, cx, z, cz, hmap) && heightAt(x, cx, z-1, cz, hmap) < heightAt(x, cx, z, cz, hmap))
				return false;
	
			if (heightAt(x, cx, z, cz, hmap)-heightAt(x+2, cx, z, cz, hmap) > 4)
				return true;
			
			
			if (heightAt(x, cx, z, cz, hmap)-heightAt(x-2, cx, z, cz, hmap) > 4)
				return true;
			
			
			if (heightAt(x, cx, z, cz, hmap)-heightAt(x, cx, z+2, cz, hmap) > 4)
				return true;
			
			
			if (heightAt(x, cx, z, cz, hmap)-heightAt(x, cx, z-2, cz, hmap) > 4)
				return true;
			
			
			return false;
	}
}
